package com.liyunc.demo.bundle.security.filter;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.google.common.base.Strings;
import com.liyunc.demo.bundle.security.model.BaseCredential;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * @author liyuncong
 * @version 1.0
 * @file BaseAuthFilter.java
 * @brief BaseAuthFilter
 * @details BaseAuthFilter
 * @date 2022-03-26
 *
 * Edit History
 * ----------------------------------------------------------------------------
 * DATE                     NAME               DESCRIPTION
 * 2022-03-26                 liyuncong        Created
 */
@Slf4j
@Component
public class BaseAuthFilter extends AbstractAuthFilter {

    public BaseAuthFilter() {
        super(true);
    }

    @Override
    protected String getAuthFilterName() {
        return this.getClass().getName();
    }

    @Override
    protected Boolean matchesLoginAttempt(HttpServletRequest request) {
        if (Objects.isNull(request)) {
            return Boolean.FALSE;
        }
        // TODO login uri need use properties
        String loginUri = "/api/v1/token";
        String currentUri = request.getRequestURI();
        if (Objects.isNull(currentUri)) {
            return Boolean.FALSE;
        }
        return currentUri.equalsIgnoreCase(loginUri);
    }

    @Override
    protected LoginAttempt matchesAuthCredential(
        HttpServletRequest request, HttpServletResponse response
    ) throws IOException {
        final String contentType = request.getContentType();
        final Optional<MediaType> mediaType =
            Optional
                .ofNullable(contentType)
                .stream()
                .map(MediaType::parseMediaType)
                .findFirst();
        if (!HttpMethod.POST.matches(request.getMethod())) {
            return LoginAttempt.of(Boolean.TRUE, Boolean.FALSE, null);
        }
        if (mediaType.stream().anyMatch(MediaType.APPLICATION_JSON::isCompatibleWith)) {
            return this.readAuthFromJson(request);
        } else {
            return this.readAuthFromForm(request);
        }
    }

    protected LoginAttempt readAuthFromJson(
        HttpServletRequest request) throws IOException {
        InputStream inputStream = request.getInputStream();
        if (inputStream.available() < 1) {
            return LoginAttempt.of(Boolean.FALSE, Boolean.TRUE, null);
        }
        String json = null;
        try (BufferedReader reader = new BufferedReader(
            new InputStreamReader(inputStream, StandardCharsets.UTF_8)
        )) {
            json = reader.readLine();
            Map<String, String> dataMap = objectMapper()
                .readValue(json, new TypeReference<>() {
                });
            String username = dataMap.get("username");
            String password = dataMap.get("password");
            return this.buildAuthCredential(username, password);
        } catch (MismatchedInputException exception) {
            log.warn("Failed to read auth from json data {}", json, exception);
        }
        return LoginAttempt.of(Boolean.FALSE, Boolean.TRUE, null);
    }

    protected LoginAttempt readAuthFromForm(HttpServletRequest request) {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        return this.buildAuthCredential(username, password);
    }

    private LoginAttempt buildAuthCredential(
        final String username, final String password
    ) {
        if (Strings.isNullOrEmpty(username) || Strings.isNullOrEmpty(password)) {
            return LoginAttempt.of(Boolean.FALSE, Boolean.TRUE, null);
        }
        return LoginAttempt.of(Boolean.FALSE, Boolean.TRUE,
            new BaseCredential<>(username, password));
    }
}
